package com.jingdianjichi.util;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.hutool.json.JSONObject;

import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

public class WxUtil {

    // 微信公众号 获取 access_token 接口地址
    private static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";

    // 通过 appId 与 appSecret 获取 access_token，仅返回 token 字符串
    public static String getAccessToken(String appId, String appSecret) {
        if (StrUtil.hasBlank(appId, appSecret)) {
            throw new IllegalArgumentException("appId or appSecret is blank");
        }
        Map<String, Object> params = new HashMap<>();
        params.put("grant_type", "client_credential");
        params.put("appid", appId);
        params.put("secret", appSecret);

        String resp = HttpUtil.get(ACCESS_TOKEN_URL, params);
        if (StrUtil.isBlank(resp)) {
            throw new RuntimeException("Empty response from WeChat API");
        }
        JSONObject json = JSONUtil.parseObj(resp);
        String token = json.getStr("access_token");
        if (StrUtil.isNotBlank(token)) {
            return token;
        }
        Integer errcode = json.getInt("errcode");
        String errmsg = json.getStr("errmsg");
        throw new RuntimeException("Failed to get access_token, errcode=" + errcode + ", errmsg=" + errmsg);
    }

    // ------------------------------------------------------------------------------------
    // XML 解析与封装（严格遵循 WECHAT_CALLBACK_GUIDE.md所述的微信消息数据格式 / XML 根节点为 <xml>）
    // ------------------------------------------------------------------------------------

    /**
     * 解析微信服务器推送的 XML 文本为 Map<String, String>。
     * - 支持常规消息与事件推送的字段；
     * - 保留所有字段（包含嵌套结构会以点号路径展开，如 ScanCodeInfo.ScanType）；
     * - 如果同级存在重复子节点（如 Articles.item 多条），使用 [index] 编号（从 0 开始），例如：Articles.item[0].Title。
     * 安全性：禁用外部实体，防止 XXE。
     */
    public static Map<String, String> parseWeChatXml(String xml) {
        if (StrUtil.isBlank(xml)) {
            throw new IllegalArgumentException("xml content is blank");
        }
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            // 安全设置，防止 XXE
            dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
            dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
            dbf.setXIncludeAware(false);
            dbf.setExpandEntityReferences(false);

            DocumentBuilder builder = dbf.newDocumentBuilder();
            InputSource is = new InputSource(new StringReader(xml));
            Document doc = builder.parse(is);
            Element root = doc.getDocumentElement();
            if (root == null || !"xml".equalsIgnoreCase(root.getNodeName())) {
                throw new IllegalArgumentException("Invalid WeChat XML: root element <xml> not found");
            }

            Map<String, String> result = new LinkedHashMap<>();
            // 递归展开所有元素
            traverseAndCollect(root, "", result);
            return result;
        } catch (Exception e) {
            throw new RuntimeException("Failed to parse WeChat XML", e);
        }
    }

    /**
     * 将参数 Map 封装为符合微信被动回复规范的 XML 字符串。
     * - 根节点固定为 <xml>；
     * - 文本字段使用 <![CDATA[]]> 包裹；纯数字字段以文本节点输出；
     * - 支持使用点号路径表示嵌套（如 Image.MediaId -> <Image><MediaId>..</MediaId></Image>）。
     *   注：数组/重复节点（如 item[0]）在本方法中将忽略具体下标，只生成第一个同名节点；如需复杂图文请在上层构造 DOM 或使用专用方法。
     */
    public static String buildWeChatXml(Map<String, String> params) {
        if (params == null || params.isEmpty()) {
            throw new IllegalArgumentException("params is empty");
        }
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            // 同样关闭外部实体
            dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
            dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
            dbf.setXIncludeAware(false);
            dbf.setExpandEntityReferences(false);

            DocumentBuilder builder = dbf.newDocumentBuilder();
            Document doc = builder.newDocument();
            Element root = doc.createElement("xml");
            doc.appendChild(root);

            for (Map.Entry<String, String> entry : params.entrySet()) {
                String keyPath = entry.getKey();
                if (StrUtil.isBlank(keyPath) || "xml".equalsIgnoreCase(keyPath)) {
                    continue;
                }
                String value = entry.getValue();
                createPathAndSetValue(doc, root, keyPath, value);
            }

            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer transformer = tf.newTransformer();
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            transformer.setOutputProperty(OutputKeys.INDENT, "no");

            StringWriter writer = new StringWriter();
            transformer.transform(new DOMSource(doc), new StreamResult(writer));
            return writer.toString();
        } catch (Exception e) {
            throw new RuntimeException("Failed to build WeChat XML", e);
        }
    }

    // ============================ 内部辅助方法 ============================

    private static void traverseAndCollect(Element element, String path, Map<String, String> out) {
        NodeList children = element.getChildNodes();
        // 统计当前层每个元素名出现次数
        Map<String, Integer> nameCount = new HashMap<>();
        for (int i = 0; i < children.getLength(); i++) {
            Node n = children.item(i);
            if (n.getNodeType() == Node.ELEMENT_NODE) {
                String name = n.getNodeName();
                nameCount.put(name, nameCount.getOrDefault(name, 0) + 1);
            }
        }
        // 遍历并递归
        Map<String, Integer> nameIndex = new HashMap<>();
        for (int i = 0; i < children.getLength(); i++) {
            Node n = children.item(i);
            if (n.getNodeType() != Node.ELEMENT_NODE) {
                continue;
            }
            Element child = (Element) n;
            String name = child.getNodeName();
            int total = nameCount.getOrDefault(name, 1);
            int idx = nameIndex.getOrDefault(name, 0);
            nameIndex.put(name, idx + 1);

            String key = StrUtil.isBlank(path) ? name : path + "." + name;
            if (total > 1) {
                key = key + "[" + idx + "]"; // 对重复的兄弟元素加下标
            }

            boolean hasElementChildren = hasElementChildren(child);
            if (!hasElementChildren) {
                String text = StrUtil.nullToEmpty(child.getTextContent());
                out.put(key, text);
            } else {
                traverseAndCollect(child, key, out);
            }
        }
    }

    private static boolean hasElementChildren(Element e) {
        NodeList children = e.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
                return true;
            }
        }
        return false;
    }

    private static void createPathAndSetValue(Document doc, Element root, String rawKeyPath, String value) {
        // 去掉下标（[0]）以构造层级；支持点号路径
        String[] parts = rawKeyPath.split("\\.");
        Element current = root;
        for (int i = 0; i < parts.length; i++) {
            String token = parts[i];
            // 移除数组下标（如果存在）
            token = token.replaceAll("\\[\\d+\\]", "");
            if (StrUtil.isBlank(token)) {
                continue;
            }
            Element next = findFirstChildByName(current, token);
            if (next == null) {
                next = doc.createElement(token);
                current.appendChild(next);
            }
            current = next;
        }
        // 设置叶子文本内容：数字 -> 文本；其余 -> CDATA
        if (value == null) value = "";
        if (isNumeric(value)) {
            current.setTextContent(value);
        } else {
            // 清空现有文本子节点
            removeAllChildTextNodes(current);
            CDATASection cdata = doc.createCDATASection(value);
            current.appendChild(cdata);
        }
    }

    private static Element findFirstChildByName(Element parent, String name) {
        NodeList children = parent.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node n = children.item(i);
            if (n.getNodeType() == Node.ELEMENT_NODE && name.equals(n.getNodeName())) {
                return (Element) n;
            }
        }
        return null;
    }

    private static void removeAllChildTextNodes(Element element) {
        NodeList children = element.getChildNodes();
        for (int i = children.getLength() - 1; i >= 0; i--) {
            Node n = children.item(i);
            if (n.getNodeType() == Node.TEXT_NODE || n.getNodeType() == Node.CDATA_SECTION_NODE) {
                element.removeChild(n);
            }
        }
    }

    private static boolean isNumeric(String s) {
        if (StrUtil.isBlank(s)) return false;
        return s.matches("-?\\d+(\\.\\d+)?");
    }
}
